package algo_annotations.data.contrainte_superposition;

import algo_annotations.data.Data_a;
import algo_annotations.params.Params_a;
import commons.params_appli.ParamsAppli;
import commons.data.Zone;
import commons.errors.ErrorsHandler;
import commons.spatialindex.simplegrid.SimpleGridSPI;
import commons.utils.Utils.Side;
import commons.utils.UtilsGeom;
import commons.utils.primitive_geom.Point_d;
import commons.utils.primitive_geom.Rect_d;
import commons.utils.primitive_geom.Vector_d;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;

public class GroupeSegmentFixe {
	Data_a data;
	public ArrayList<SegmentFixe> vSegment;
	public ArrayList<SegmentFixe> vSegment_optimise;

	private SimpleGridSPI spiSegment_Optim;
	private SimpleGridSPI spiSegment_NonOptim; // pour affichage debug
	private Rect_d rectEmprise;

	public boolean hasBeenOptimized() {return spiSegment_Optim!=null;} // pour du assert


	public GroupeSegmentFixe(Data_a data) {
		this.data = data;
		vSegment = new ArrayList<SegmentFixe>(10000);
	}

	public void addSegment (SegmentFixe seg) {
		vSegment.add(seg);
	}

	public void optimze (Params_a params) {
		if (vSegment.isEmpty()) return;
		computeRectEmprise();
		if (rectEmprise == null) return;

		spiSegment_NonOptim = new SimpleGridSPI("SPI Segments non optimises", rectEmprise, SimpleGridSPI.getTailleFor(vSegment.size()));
		spiSegment_NonOptim.addObjects(vSegment);

		vSegment_optimise = new ArrayList<SegmentFixe>(vSegment.size()/2);

		// Regroupement des segments en file
		ArrayList<ArrayDeque<SegmentFixe>> vFileSegment = regroupeSegmentEnFile();
		if (ErrorsHandler.assertsEnabled) checkFileSegConnected(vFileSegment);

		// Pour chaque file, on lance un petit DouglasPeucker
		for (ArrayDeque<SegmentFixe> file : vFileSegment)
			vSegment_optimise.addAll (simplifieDouglasPeucker (file, params));

		// Une fois optimisé, on peut décaller et épaissir les segments qui ont une dissymétrie
		// Si on le faisait avant, cette opération pouvant rendre les segments non contigus,
		//  l'optimisation serait impossible
		grossitEtDecaleSegmentOptimiseAvecDissymetrie();

		// En fin d'optimisation, on rempli le SPI des segments optimises et on fait qques precalculs
		spiSegment_Optim = new SimpleGridSPI("SPI Segments optimises", rectEmprise, SimpleGridSPI.getTailleFor(vSegment_optimise.size()));
		spiSegment_Optim.addObjects(vSegment_optimise);

		// Enfin, pour garantir un affichage différentié des segments optimises ou non
		//  on reutilise la variable bIsOptmized
		for (SegmentFixe seg : vSegment) seg.bIsOptimized = false;
		for (SegmentFixe seg : vSegment_optimise) seg.bIsOptimized = true;

		computeLargestSegment();
	}


	private ArrayList<ArrayDeque<SegmentFixe>> regroupeSegmentEnFile() {
		for (SegmentFixe seg : vSegment) seg.bIsOptimized = false;

		ArrayList<ArrayDeque<SegmentFixe>> vRes = new ArrayList<ArrayDeque<SegmentFixe>>();
		for (SegmentFixe seg : vSegment) {
			if (seg.bIsOptimized) continue;
			ArrayDeque<SegmentFixe> fileSeg = new ArrayDeque<SegmentFixe>();
			fileSeg.add(seg);
			seg.bIsOptimized = true;
			completeFileSegment_rec(fileSeg, true);
			completeFileSegment_rec(fileSeg, false);

			vRes.add(fileSeg);
		}
		return vRes;
	}

	private void completeFileSegment_rec(ArrayDeque<SegmentFixe> fileSeg, boolean bSensAvant) {
		if (bSensAvant) {

			SegmentFixe segExtr = fileSeg.getLast();
			Point_d ptExtr = segExtr.pt2;
			ArrayList<SegmentFixe> vSegmentProche = (ArrayList) spiSegment_NonOptim.getObjectsThatMayIntersectWith(ptExtr);
			for (SegmentFixe seg : vSegmentProche) {

				if (seg.bIsOptimized)
					continue;
				if (seg == segExtr)
					continue;
				if (segExtr.getEchelleGeneralization().echelle_inv != seg.getEchelleGeneralization().echelle_inv ||
						!segExtr.contrainteGeom.equals(seg.contrainteGeom))
					continue;
				
				// Si les segments ont une dissymetrie, on oblige qu'ils soient dans le même sens
				// Sinon, on essaie d'inverser les segments si ca peut aider
				if (segExtr.contrainteGeom.hasDissymetrie()) {
					if (!segExtr.pt2.equalsXY(seg.pt1))
						continue;
				} else {
					if (segExtr.pt2.equalsXY(seg.pt1))
						; // OK, cas normal
					else if(segExtr.pt2.equalsXY(seg.pt2))
						seg.invert();
						// on continue, c'est désormais OK
					else
						continue; // segments non connectés
				}

				// Ici tout est OK, seg peut être ajouté en fin de file
				fileSeg.addLast(seg);
				seg.bIsOptimized = true;
				completeFileSegment_rec(fileSeg, bSensAvant);
				break;
			}
		} else { // sens arrière
			SegmentFixe segExtr = fileSeg.getFirst();
			Point_d ptExtr = segExtr.pt1;
			ArrayList<SegmentFixe> vSegmentProche = (ArrayList) spiSegment_NonOptim.getObjectsThatMayIntersectWith(ptExtr);
			for (SegmentFixe seg : vSegmentProche) {

				if (seg.bIsOptimized)
					continue;
				if (seg == segExtr)
					continue;
				if (segExtr.getEchelleGeneralization().echelle_inv != seg.getEchelleGeneralization().echelle_inv ||
						!segExtr.contrainteGeom.equals(seg.contrainteGeom))
					continue;

				// Si les segments ont une dissymetrie, on oblige qu'ils soient dans le même sens
				// Sinon, on essaie d'inverser les segments si ca peut aider
				if (segExtr.contrainteGeom.hasDissymetrie()) {
					if (!segExtr.pt1.equalsXY(seg.pt2))
						continue;
				} else {
					if (segExtr.pt1.equalsXY(seg.pt2))
						; // OK, cas normal
					else if(segExtr.pt1.equalsXY(seg.pt1))
						seg.invert();
						// on continue, c'set désormais OK
					else
						continue; // segments non connectés
				}

				// Ici tout est OK, seg peut être ajouté en début de file
				fileSeg.addFirst(seg);
				seg.bIsOptimized = true;
				completeFileSegment_rec(fileSeg, bSensAvant);
				break;
			}
		}
	}

	// Check pour du assert
	private void checkFileSegConnected(ArrayList<ArrayDeque<SegmentFixe>> vFileSeg) {
		// Tous les segments ont ils été traités ?
		for (SegmentFixe seg : vSegment)
			assert(seg.bIsOptimized) : "Segment non optimisé";

		// Le nombre total est-il OK ?
		int nbSegInFile = 0;
		for (ArrayDeque<SegmentFixe> file : vFileSeg)
			nbSegInFile += file.size();
		assert(nbSegInFile == vSegment.size()) : "Nombre de segment incorrect : " + nbSegInFile + " / " + vSegment.size();

		// Le sens des segment est-il OK ?
		for (ArrayDeque<SegmentFixe> file : vFileSeg) {
			ArrayList<SegmentFixe> vSeg = new ArrayList<SegmentFixe>(file);
			for (int i=0; i <vSeg.size()-1 ; i++) {
				SegmentFixe seg1 = vSeg.get(i);
				SegmentFixe seg2 = vSeg.get(i+1);
				assert(seg1.pt2.equalsXY(seg2.pt1)) : "Segments mal orientés";
			}
		}
		
	}


	private ArrayList<SegmentFixe> simplifieDouglasPeucker (ArrayDeque<SegmentFixe> fileSeg, Params_a params) {
		// 1 seul segment, on le renvoie (une copie, car un segment optimise risque d'être déplacé et grossi s'il a une dissymétrie)
		if (fileSeg.size()==1) {
			ArrayList<SegmentFixe> vRes = new ArrayList<SegmentFixe>();
			vRes.add(new SegmentFixe(fileSeg.getFirst(), params));
			return vRes;
		}

		// 1) On transforme la file de segments en liste de points
		ArrayList<Point_d> vPoints = new ArrayList<Point_d>(fileSeg.size()+1);
		vPoints.add(fileSeg.getFirst().pt1);
		for (SegmentFixe seg : fileSeg)
			vPoints.add(seg.pt2);
		

		SegmentFixe oneSeg = fileSeg.getFirst(); // pour récupérer des attributs communs pour les remettre dans les nouveaux segments

		// 2) On optimise
		// attention : la tolerance depend de la zone (de l'échelle de généralisation, plus précisément)
		double tolerance = params.toleranceSegment_papier_mm/1000 * oneSeg.getEchelleGeneralization().echelle_inv;
		ArrayList<Point_d> vPoint_optimized = UtilsGeom.douglasPeuckerReduction_global(vPoints, tolerance);

		// 3) On reconstitue une liste de segment
		ArrayList<SegmentFixe> vRes = new ArrayList<SegmentFixe>(vPoint_optimized.size()-1);

		for (int i=0; i<vPoint_optimized.size()-1; i++) {
			Point_d pt1 = vPoint_optimized.get(i);
			Point_d pt2 = vPoint_optimized.get(i+1);

			// Init a partir du 1er segment, pour tout ce qui est attributaire commun (zones, contrainteGeom, etc.)
			SegmentFixe seg = new SegmentFixe(oneSeg, params);
			// Puis on lui met les bonnes coordonnées
			seg.pt1 = pt1;
			seg.pt2 = pt2;
			seg.init(); // obligatoire, precalcul le boundingbox pour optimisation ultérieure

			vRes.add(seg);
		}

		return vRes;
	}



	private void grossitEtDecaleSegmentOptimiseAvecDissymetrie() {
		for (SegmentFixe seg : vSegment_optimise) {
			// On ne traite que les segments optimises et avec dissymétrie
			if (!seg.contrainteGeom.hasDissymetrie()) continue;

			// Si le segment pointe vers des coordonnées communes avec un autre seg
			//  le déplacement va affecter les 2.
			// Dans le doute, on coupe le lien
			seg.supprLinkCoord();
			Vector_d v = new Vector_d(seg);
			// Vecteur normal unitaire
			Vector_d vNormal;
			if (seg.contrainteGeom.sideDissymetrie == Side.RIGHT)
				vNormal = v.getVectUnitaireNormalDroite();
			else
				vNormal = v.getVectUnitaireNormalGauche();
			if (vNormal==null) // segment vide ?
				continue;

			double largeurTotale_mm = seg.contrainteGeom.epaisseur_papier_mm + seg.contrainteGeom.epaisseur_dissymetrie_mm;
			double lgDeplacement_mm = -seg.contrainteGeom.epaisseur_papier_mm/2 + largeurTotale_mm/2;

			double lgDeplacement_m = lgDeplacement_mm/1000 * seg.eg.echelle_inv;
			vNormal.multiplyBy(lgDeplacement_m);

			// Déplacement
			seg.pt1.x += vNormal.pt2.x - vNormal.pt1.x;
			seg.pt1.y += vNormal.pt2.y - vNormal.pt1.y;

			seg.pt2.x += vNormal.pt2.x - vNormal.pt1.x;
			seg.pt2.y += vNormal.pt2.y - vNormal.pt1.y;
			// recalcul du bounding box
			seg.init();


			// Grossissage
			double largeurTotale_m = largeurTotale_mm/1000 * seg.eg.echelle_inv;
			seg.setLargeurSeg_reel_m(largeurTotale_m);
		}
	}

	private double largeurSegment_reel_m;
	private void computeLargestSegment() {
		largeurSegment_reel_m = 0;
		for (SegmentFixe seg : vSegment)
			largeurSegment_reel_m = Math.max(largeurSegment_reel_m, seg.getLargeurSeg_reel_m());

	}
	public double getLargestSegment_reel_mm() {
		return largeurSegment_reel_m;
	}

	private void computeRectEmprise() {
		if (vSegment.isEmpty())
			rectEmprise = null;
		rectEmprise = new Rect_d(vSegment.get(0).pt1);
		for (SegmentFixe seg : vSegment) {
			rectEmprise.extendToPoint(seg.pt1);
			rectEmprise.extendToPoint(seg.pt2);
		}
	}

	public Rect_d getRectEmprise() {
		return rectEmprise;
	}

	public ArrayList getSegNonOptimThaMayIntersectWith(Rect_d rectVisu) {
		if (spiSegment_NonOptim != null)
			return spiSegment_NonOptim.getObjectsThatMayIntersectWith(rectVisu);
		else
			return new ArrayList();
	}
	public ArrayList getSegOptimThaMayIntersectWith(Rect_d rectVisu) {
		if (spiSegment_Optim != null)
			return spiSegment_Optim.getObjectsThatMayIntersectWith(rectVisu);
		else
			return new ArrayList();
	}

}
